// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008-2009 Guillaume Saupin <guillaume.saupin@cea.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

#ifndef EIGEN_SKYLINE_STORAGE_H
#define EIGEN_SKYLINE_STORAGE_H

namespace Eigen {

/** Stores a skyline set of values in three structures :
 * The diagonal elements
 * The upper elements
 * The lower elements
 *
 */
template <typename Scalar> class SkylineStorage
{
    typedef typename NumTraits<Scalar>::Real RealScalar;
    typedef SparseIndex Index;

public:
    SkylineStorage()
        : m_diag(0), m_lower(0), m_upper(0), m_lowerProfile(0), m_upperProfile(0), m_diagSize(0), m_upperSize(0), m_lowerSize(0), m_upperProfileSize(0),
          m_lowerProfileSize(0), m_allocatedSize(0)
    {
    }

    SkylineStorage(const SkylineStorage& other)
        : m_diag(0), m_lower(0), m_upper(0), m_lowerProfile(0), m_upperProfile(0), m_diagSize(0), m_upperSize(0), m_lowerSize(0), m_upperProfileSize(0),
          m_lowerProfileSize(0), m_allocatedSize(0)
    {
        *this = other;
    }

    SkylineStorage& operator=(const SkylineStorage& other)
    {
        resize(other.diagSize(), other.m_upperProfileSize, other.m_lowerProfileSize, other.upperSize(), other.lowerSize());
        memcpy(m_diag, other.m_diag, m_diagSize * sizeof(Scalar));
        memcpy(m_upper, other.m_upper, other.upperSize() * sizeof(Scalar));
        memcpy(m_lower, other.m_lower, other.lowerSize() * sizeof(Scalar));
        memcpy(m_upperProfile, other.m_upperProfile, m_upperProfileSize * sizeof(Index));
        memcpy(m_lowerProfile, other.m_lowerProfile, m_lowerProfileSize * sizeof(Index));
        return *this;
    }

    void swap(SkylineStorage& other)
    {
        std::swap(m_diag, other.m_diag);
        std::swap(m_upper, other.m_upper);
        std::swap(m_lower, other.m_lower);
        std::swap(m_upperProfile, other.m_upperProfile);
        std::swap(m_lowerProfile, other.m_lowerProfile);
        std::swap(m_diagSize, other.m_diagSize);
        std::swap(m_upperSize, other.m_upperSize);
        std::swap(m_lowerSize, other.m_lowerSize);
        std::swap(m_allocatedSize, other.m_allocatedSize);
    }

    ~SkylineStorage()
    {
        delete[] m_diag;
        delete[] m_upper;
        if (m_upper != m_lower)
            delete[] m_lower;
        delete[] m_upperProfile;
        delete[] m_lowerProfile;
    }

    void reserve(Index size, Index upperProfileSize, Index lowerProfileSize, Index upperSize, Index lowerSize)
    {
        Index newAllocatedSize = size + upperSize + lowerSize;
        if (newAllocatedSize > m_allocatedSize)
            reallocate(size, upperProfileSize, lowerProfileSize, upperSize, lowerSize);
    }

    void squeeze()
    {
        if (m_allocatedSize > m_diagSize + m_upperSize + m_lowerSize)
            reallocate(m_diagSize, m_upperProfileSize, m_lowerProfileSize, m_upperSize, m_lowerSize);
    }

    void resize(Index diagSize, Index upperProfileSize, Index lowerProfileSize, Index upperSize, Index lowerSize, float reserveSizeFactor = 0)
    {
        if (m_allocatedSize < diagSize + upperSize + lowerSize)
            reallocate(diagSize,
                       upperProfileSize,
                       lowerProfileSize,
                       upperSize + Index(reserveSizeFactor * upperSize),
                       lowerSize + Index(reserveSizeFactor * lowerSize));
        m_diagSize = diagSize;
        m_upperSize = upperSize;
        m_lowerSize = lowerSize;
        m_upperProfileSize = upperProfileSize;
        m_lowerProfileSize = lowerProfileSize;
    }

    inline Index diagSize() const { return m_diagSize; }

    inline Index upperSize() const { return m_upperSize; }

    inline Index lowerSize() const { return m_lowerSize; }

    inline Index upperProfileSize() const { return m_upperProfileSize; }

    inline Index lowerProfileSize() const { return m_lowerProfileSize; }

    inline Index allocatedSize() const { return m_allocatedSize; }

    inline void clear() { m_diagSize = 0; }

    inline Scalar& diag(Index i) { return m_diag[i]; }

    inline const Scalar& diag(Index i) const { return m_diag[i]; }

    inline Scalar& upper(Index i) { return m_upper[i]; }

    inline const Scalar& upper(Index i) const { return m_upper[i]; }

    inline Scalar& lower(Index i) { return m_lower[i]; }

    inline const Scalar& lower(Index i) const { return m_lower[i]; }

    inline Index& upperProfile(Index i) { return m_upperProfile[i]; }

    inline const Index& upperProfile(Index i) const { return m_upperProfile[i]; }

    inline Index& lowerProfile(Index i) { return m_lowerProfile[i]; }

    inline const Index& lowerProfile(Index i) const { return m_lowerProfile[i]; }

    static SkylineStorage
    Map(Index* upperProfile, Index* lowerProfile, Scalar* diag, Scalar* upper, Scalar* lower, Index size, Index upperSize, Index lowerSize)
    {
        SkylineStorage res;
        res.m_upperProfile = upperProfile;
        res.m_lowerProfile = lowerProfile;
        res.m_diag = diag;
        res.m_upper = upper;
        res.m_lower = lower;
        res.m_allocatedSize = res.m_diagSize = size;
        res.m_upperSize = upperSize;
        res.m_lowerSize = lowerSize;
        return res;
    }

    inline void reset()
    {
        memset(m_diag, 0, m_diagSize * sizeof(Scalar));
        memset(m_upper, 0, m_upperSize * sizeof(Scalar));
        memset(m_lower, 0, m_lowerSize * sizeof(Scalar));
        memset(m_upperProfile, 0, m_diagSize * sizeof(Index));
        memset(m_lowerProfile, 0, m_diagSize * sizeof(Index));
    }

    void prune(Scalar reference, RealScalar epsilon = dummy_precision<RealScalar>())
    {
        //TODO
    }

protected:
    inline void reallocate(Index diagSize, Index upperProfileSize, Index lowerProfileSize, Index upperSize, Index lowerSize)
    {
        Scalar* diag = new Scalar[diagSize];
        Scalar* upper = new Scalar[upperSize];
        Scalar* lower = new Scalar[lowerSize];
        Index* upperProfile = new Index[upperProfileSize];
        Index* lowerProfile = new Index[lowerProfileSize];

        Index copyDiagSize = (std::min)(diagSize, m_diagSize);
        Index copyUpperSize = (std::min)(upperSize, m_upperSize);
        Index copyLowerSize = (std::min)(lowerSize, m_lowerSize);
        Index copyUpperProfileSize = (std::min)(upperProfileSize, m_upperProfileSize);
        Index copyLowerProfileSize = (std::min)(lowerProfileSize, m_lowerProfileSize);

        // copy
        memcpy(diag, m_diag, copyDiagSize * sizeof(Scalar));
        memcpy(upper, m_upper, copyUpperSize * sizeof(Scalar));
        memcpy(lower, m_lower, copyLowerSize * sizeof(Scalar));
        memcpy(upperProfile, m_upperProfile, copyUpperProfileSize * sizeof(Index));
        memcpy(lowerProfile, m_lowerProfile, copyLowerProfileSize * sizeof(Index));

        // delete old stuff
        delete[] m_diag;
        delete[] m_upper;
        delete[] m_lower;
        delete[] m_upperProfile;
        delete[] m_lowerProfile;
        m_diag = diag;
        m_upper = upper;
        m_lower = lower;
        m_upperProfile = upperProfile;
        m_lowerProfile = lowerProfile;
        m_allocatedSize = diagSize + upperSize + lowerSize;
        m_upperSize = upperSize;
        m_lowerSize = lowerSize;
    }

public:
    Scalar* m_diag;
    Scalar* m_upper;
    Scalar* m_lower;
    Index* m_upperProfile;
    Index* m_lowerProfile;
    Index m_diagSize;
    Index m_upperSize;
    Index m_lowerSize;
    Index m_upperProfileSize;
    Index m_lowerProfileSize;
    Index m_allocatedSize;
};

}  // end namespace Eigen

#endif  // EIGEN_SKYLINE_STORAGE_H
